﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;
using System.Security;
using Microsoft.Win32;

namespace System;

public static partial class PlatformDetection
{
    public static bool IsNetFramework => RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase);
    public static Version OSXVersion => throw new PlatformNotSupportedException();
    public static Version OpenSslVersion => throw new PlatformNotSupportedException();
    public static bool IsSuperUser => throw new PlatformNotSupportedException();
    public static bool IsCentos6 => false;
    public static bool IsOpenSUSE => false;
    public static bool IsUbuntu => false;
    public static bool IsDebian => false;
    public static bool IsAlpine => false;
    public static bool IsDebian8 => false;
    public static bool IsUbuntu1404 => false;
    public static bool IsUbuntu1604 => false;
    public static bool IsUbuntu1704 => false;
    public static bool IsUbuntu1710 => false;
    public static bool IsUbuntu1710OrHigher => false;
    public static bool IsUbuntu1804 => false;
    public static bool IsTizen => false;
    public static bool IsNotFedoraOrRedHatFamily => true;
    public static bool IsFedora => false;
    public static bool IsWindowsNanoServer => (IsNotWindowsIoTCore && GetInstallationType().Equals("Nano Server", StringComparison.OrdinalIgnoreCase));
    public static bool IsWindowsServerCore => GetInstallationType().Equals("Server Core", StringComparison.OrdinalIgnoreCase);
    public static int WindowsVersion => GetWindowsVersion();
    public static bool IsMacOsHighSierraOrHigher { get; }
    public static Version ICUVersion => new(0, 0, 0, 0);
    public static bool IsRedHatFamily => false;
    public static bool IsNotRedHatFamily => true;
    public static bool IsRedHatFamily6 => false;
    public static bool IsRedHatFamily7 => false;
    public static bool IsNotRedHatFamily6 => true;

    public static bool IsWindows10Version1607OrGreater =>
        GetWindowsVersion() == 10 && GetWindowsMinorVersion() == 0 && GetWindowsBuildNumber() >= 14393;
    public static bool IsWindows10Version1703OrGreater =>
        GetWindowsVersion() == 10 && GetWindowsMinorVersion() == 0 && GetWindowsBuildNumber() >= 15063;
    public static bool IsWindows10Version1709OrGreater =>
        GetWindowsVersion() == 10 && GetWindowsMinorVersion() == 0 && GetWindowsBuildNumber() >= 16299;
    public static bool IsWindows10Version1803OrGreater =>
        GetWindowsVersion() == 10 && GetWindowsMinorVersion() == 0 && GetWindowsBuildNumber() >= 17134;
    public static bool IsWindows11OrHigher =>
        GetWindowsVersion() == 10 && GetWindowsMinorVersion() == 0 && GetWindowsBuildNumber() >= 22000;

    // Windows OneCoreUAP SKU doesn't have httpapi.dll
    public static bool IsNotOneCoreUAP =>
        File.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "System32", "httpapi.dll"));

    public static bool IsWindowsIoTCore => GetWindowsProductType() is PRODUCT_IOTUAPCOMMERCIAL or PRODUCT_IOTUAP;

    public static bool IsWindowsHomeEdition => GetWindowsProductType() switch
    {
        PRODUCT_CORE
            or PRODUCT_CORE_COUNTRYSPECIFIC
            or PRODUCT_CORE_N
            or PRODUCT_CORE_SINGLELANGUAGE
            or PRODUCT_HOME_BASIC
            or PRODUCT_HOME_BASIC_N
            or PRODUCT_HOME_PREMIUM
            or PRODUCT_HOME_PREMIUM_N => true,
        _ => false,
    };

    public static bool IsWindows => true;
    public static bool IsWindows7 => GetWindowsVersion() == 6 && GetWindowsMinorVersion() == 1;
    public static bool IsNotWindows7 => !IsWindows7;
    public static bool IsWindows8x => GetWindowsVersion() == 6 && (GetWindowsMinorVersion() == 2 || GetWindowsMinorVersion() == 3);

    public static string LibcRelease => "glibc_not_found";
    public static string LibcVersion => "glibc_not_found";

    public static string GetDistroVersionString() => $"WindowsProductType={GetWindowsProductType()} WindowsInstallationType={GetInstallationType()}";

    private static int s_isInAppContainer = -1;

    public static bool IsInAppContainer
    {
        // This actually checks whether code is running in a modern app.
        // Currently this is the only situation where we run in app container.
        // If we want to distinguish the two cases in future,
        // EnvironmentHelpers.IsAppContainerProcess in desktop code shows how to check for the AC token.
        get
        {
            if (s_isInAppContainer != -1)
            {
                return s_isInAppContainer == 1;
            }

            if (!IsWindows || IsWindows7)
            {
                s_isInAppContainer = 0;
                return false;
            }

            byte[] buffer = [];
            uint bufferSize = 0;
            try
            {
                int result = GetCurrentApplicationUserModelId(ref bufferSize, buffer);
                s_isInAppContainer = result switch
                {
                    // APPMODEL_ERROR_NO_APPLICATION
                    15703 => 0,
                    // ERROR_SUCCESS
                    0 or 122 => 1,// Success is actually insufficent buffer as we're really only looking for
                                  // not NO_APPLICATION and we're not actually giving a buffer here. The
                                  // API will always return NO_APPLICATION if we're not running under a
                                  // WinRT process, no matter what size the buffer is.
                    _ => throw new InvalidOperationException($"Failed to get AppId, result was {result}."),
                };
            }
            catch (Exception e)
            {
                // We could catch this here, being friendly with older portable surface area should we
                // desire to use this method elsewhere.
                if (e.GetType().FullName.Equals("System.EntryPointNotFoundException", StringComparison.Ordinal))
                {
                    // API doesn't exist, likely pre Win8
                    s_isInAppContainer = 0;
                }
                else
                {
                    throw;
                }
            }

            return s_isInAppContainer == 1;
        }
    }

    private static int s_isWindowsElevated = -1;

    public static bool IsWindowsAndElevated
    {
        get
        {
            if (s_isWindowsElevated != -1)
            {
                return s_isWindowsElevated == 1;
            }

            if (!IsWindows || IsInAppContainer)
            {
                s_isWindowsElevated = 0;
                return false;
            }

            Assert.True(OpenProcessToken(PInvoke.GetCurrentProcess(), TOKEN_READ, out IntPtr processToken));

            try
            {
                Assert.True(GetTokenInformation(
                    processToken, TokenElevation, out uint tokenInfo, sizeof(uint), out uint returnLength));

                s_isWindowsElevated = tokenInfo == 0 ? 0 : 1;
            }
            finally
            {
                PInvoke.CloseHandle((HANDLE)processToken);
            }

            return s_isWindowsElevated == 1;
        }
    }

    private static string GetInstallationType()
    {
        string key = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion";
        string value = string.Empty;

        try
        {
            value = (string)Registry.GetValue(key, "InstallationType", defaultValue: "");
        }
        catch (Exception e) when (e is SecurityException or InvalidCastException or PlatformNotSupportedException /* UAP */)
        {
        }

        return value;
    }

    private static int GetWindowsProductType()
    {
        Assert.True(GetProductInfo(Environment.OSVersion.Version.Major, Environment.OSVersion.Version.Minor, 0, 0, out int productType));
        return productType;
    }

    private static unsafe int GetWindowsMinorVersion()
    {
        RTL_OSVERSIONINFOEX osvi = new()
        {
            dwOSVersionInfoSize = (uint)sizeof(RTL_OSVERSIONINFOEX)
        };
        Assert.Equal(0, RtlGetVersion(ref osvi));
        return (int)osvi.dwMinorVersion;
    }

    private static unsafe int GetWindowsBuildNumber()
    {
        RTL_OSVERSIONINFOEX osvi = new()
        {
            dwOSVersionInfoSize = (uint)sizeof(RTL_OSVERSIONINFOEX)
        };
        Assert.Equal(0, RtlGetVersion(ref osvi));
        return (int)osvi.dwBuildNumber;
    }

    private const uint TokenElevation = 20;
    private const uint STANDARD_RIGHTS_READ = 0x00020000;
    private const uint TOKEN_QUERY = 0x0008;
    private const uint TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY;

    [DllImport("advapi32.dll", SetLastError = true, ExactSpelling = true)]
    private static extern bool GetTokenInformation(
        IntPtr TokenHandle,
        uint TokenInformationClass,
        out uint TokenInformation,
        uint TokenInformationLength,
        out uint ReturnLength);

    private const int PRODUCT_IOTUAP = 0x0000007B;
    private const int PRODUCT_IOTUAPCOMMERCIAL = 0x00000083;
    private const int PRODUCT_CORE = 0x00000065;
    private const int PRODUCT_CORE_COUNTRYSPECIFIC = 0x00000063;
    private const int PRODUCT_CORE_N = 0x00000062;
    private const int PRODUCT_CORE_SINGLELANGUAGE = 0x00000064;
    private const int PRODUCT_HOME_BASIC = 0x00000002;
    private const int PRODUCT_HOME_BASIC_N = 0x00000005;
    private const int PRODUCT_HOME_PREMIUM = 0x00000003;
    private const int PRODUCT_HOME_PREMIUM_N = 0x0000001A;

    [DllImport("kernel32.dll", SetLastError = false)]
    private static extern bool GetProductInfo(
        int dwOSMajorVersion,
        int dwOSMinorVersion,
        int dwSpMajorVersion,
        int dwSpMinorVersion,
        out int pdwReturnedProductType);

    [DllImport("ntdll.dll", ExactSpelling = true)]
    private static extern int RtlGetVersion(ref RTL_OSVERSIONINFOEX lpVersionInformation);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private unsafe struct RTL_OSVERSIONINFOEX
    {
#pragma warning disable IDE1006 // Naming Styles - matching OS
        internal uint dwOSVersionInfoSize;
        internal uint dwMajorVersion;
        internal uint dwMinorVersion;
        internal uint dwBuildNumber;
        internal uint dwPlatformId;
        internal fixed char szCSDVersion[128];
#pragma warning restore IDE1006
    }

    private static unsafe int GetWindowsVersion()
    {
        RTL_OSVERSIONINFOEX osvi = new()
        {
            dwOSVersionInfoSize = (uint)sizeof(RTL_OSVERSIONINFOEX)
        };
        Assert.Equal(0, RtlGetVersion(ref osvi));
        return (int)osvi.dwMajorVersion;
    }

    [DllImport("kernel32.dll", ExactSpelling = true)]
    private static extern int GetCurrentApplicationUserModelId(ref uint applicationUserModelIdLength, byte[] applicationUserModelId);

    [DllImport("advapi32.dll", SetLastError = true, ExactSpelling = true)]
    private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);
}
